"""
人脸检测模块
负责检测图像中的人脸位置和特征
"""

import cv2
import numpy as np
import os
import logging
from typing import List, Tuple, Dict, Optional


class FaceDetector:
    """人脸检测类
    
    提供人脸检测、特征提取和人脸识别功能
    
    Attributes:
        config: 人脸检测配置参数
        face_cascade: Haar级联分类器
        eye_cascade: 眼睛级联分类器
        smile_cascade: 微笑级联分类器
        logger: 日志记录器
    """
    
    def __init__(self, config: Dict):
        """初始化人脸检测器
        
        Args:
            config (dict): 人脸检测配置参数
            
        Raises:
            FileNotFoundError: 当级联分类器文件不存在时
        """
        self.config = config
        self.logger = logging.getLogger(__name__)
        
        # 加载级联分类器
        cascade_dir = config.get("cascade_dir", "data/haarcascades")
        
        # 人脸检测分类器
        face_cascade_path = os.path.join(cascade_dir, "haarcascade_frontalface_default.xml")
        if not os.path.exists(face_cascade_path):
            # 尝试使用OpenCV内置的级联分类器
            face_cascade_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
        
        self.face_cascade = cv2.CascadeClassifier(face_cascade_path)
        if self.face_cascade.empty():
            raise FileNotFoundError(f"无法加载人脸级联分类器: {face_cascade_path}")
        
        # 眼睛检测分类器
        eye_cascade_path = os.path.join(cascade_dir, "haarcascade_eye.xml")
        if not os.path.exists(eye_cascade_path):
            eye_cascade_path = cv2.data.haarcascades + "haarcascade_eye.xml"
            
        self.eye_cascade = cv2.CascadeClassifier(eye_cascade_path)
        if self.eye_cascade.empty():
            self.logger.warning("无法加载眼睛级联分类器")
        
        # 微笑检测分类器
        smile_cascade_path = os.path.join(cascade_dir, "haarcascade_smile.xml")
        if not os.path.exists(smile_cascade_path):
            smile_cascade_path = cv2.data.haarcascades + "haarcascade_smile.xml"
            
        self.smile_cascade = cv2.CascadeClassifier(smile_cascade_path)
        if self.smile_cascade.empty():
            self.logger.warning("无法加载微笑级联分类器")
            
        self.logger.info("人脸检测器初始化完成")
    
    def detect_faces(self, image: np.ndarray, scale_factor: float = 1.1, 
                     min_neighbors: int = 5, min_size: Tuple[int, int] = (30, 30)) -> List[Tuple[int, int, int, int]]:
        """检测图像中的人脸
        
        Args:
            image (numpy.ndarray): 输入图像
            scale_factor (float): 图像缩放因子，用于构建图像金字塔
            min_neighbors (int): 每个候选矩形应包含的邻近候选框数量
            min_size (tuple): 检测人脸的最小尺寸
            
        Returns:
            list: 检测到的人脸列表，每个元素为(x, y, w, h)格式的矩形
        """
        # 转换为灰度图像
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        
        # 直方图均衡化，提高对比度
        gray = cv2.equalizeHist(gray)
        
        # 检测人脸
        faces = self.face_cascade.detectMultiScale(
            gray,
            scaleFactor=scale_factor,
            minNeighbors=min_neighbors,
            minSize=min_size
        )
        
        self.logger.debug(f"检测到 {len(faces)} 个人脸")
        return faces
    
    def detect_eyes(self, image: np.ndarray, face_rect: Tuple[int, int, int, int]) -> List[Tuple[int, int, int, int]]:
        """在人脸区域内检测眼睛
        
        Args:
            image (numpy.ndarray): 输入图像
            face_rect (tuple): 人脸区域矩形 (x, y, w, h)
            
        Returns:
            list: 检测到的眼睛列表，每个元素为(x, y, w, h)格式的矩形
        """
        if self.eye_cascade.empty():
            return []
            
        x, y, w, h = face_rect
        face_roi = image[y:y+h, x:x+w]
        gray_roi = cv2.cvtColor(face_roi, cv2.COLOR_BGR2GRAY)
        
        # 检测眼睛
        eyes = self.eye_cascade.detectMultiScale(gray_roi)
        
        # 将眼睛坐标转换到原图坐标系
        result = []
        for (ex, ey, ew, eh) in eyes:
            result.append((x + ex, y + ey, ew, eh))
            
        return result
    
    def detect_smile(self, image: np.ndarray, face_rect: Tuple[int, int, int, int]) -> bool:
        """检测人脸区域是否有微笑
        
        Args:
            image (numpy.ndarray): 输入图像
            face_rect (tuple): 人脸区域矩形 (x, y, w, h)
            
        Returns:
            bool: 检测到微笑返回True，否则返回False
        """
        if self.smile_cascade.empty():
            return False
            
        x, y, w, h = face_rect
        # 只检测人脸下半部分，提高微笑检测准确性
        smile_roi = image[y + int(h * 0.6):y + h, x:x + w]
        gray_roi = cv2.cvtColor(smile_roi, cv2.COLOR_BGR2GRAY)
        
        # 检测微笑
        smiles = self.smile_cascade.detectMultiScale(
            gray_roi,
            scaleFactor=1.7,
            minNeighbors=22,
            minSize=(25, 25)
        )
        
        return len(smiles) > 0
    
    def get_face_features(self, image: np.ndarray, face_rect: Tuple[int, int, int, int]) -> Dict:
        """提取人脸特征
        
        Args:
            image (numpy.ndarray): 输入图像
            face_rect (tuple): 人脸区域矩形 (x, y, w, h)
            
        Returns:
            dict: 人脸特征字典，包含位置、眼睛、微笑等信息
        """
        x, y, w, h = face_rect
        
        # 检测眼睛
        eyes = self.detect_eyes(image, face_rect)
        
        # 检测微笑
        is_smiling = self.detect_smile(image, face_rect)
        
        # 计算人脸中心
        center_x = x + w // 2
        center_y = y + h // 2
        
        return {
            "position": (center_x, center_y),
            "rect": face_rect,
            "eyes": eyes,
            "is_smiling": is_smiling,
            "eye_count": len(eyes)
        }
    
    def draw_face_detections(self, image: np.ndarray, faces: List[Tuple[int, int, int, int]]) -> np.ndarray:
        """在图像上绘制人脸检测结果
        
        Args:
            image (numpy.ndarray): 输入图像
            faces (list): 人脸矩形列表
            
        Returns:
            numpy.ndarray: 绘制了检测结果的图像
        """
        result_image = image.copy()
        
        for (x, y, w, h) in faces:
            # 绘制人脸矩形
            cv2.rectangle(result_image, (x, y), (x + w, y + h), (255, 0, 0), 2)
            
            # 获取人脸特征
            features = self.get_face_features(image, (x, y, w, h))
            
            # 绘制眼睛
            for (ex, ey, ew, eh) in features["eyes"]:
                cv2.rectangle(result_image, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)
            
            # 绘制人脸中心点
            cv2.circle(result_image, features["position"], 3, (0, 0, 255), -1)
            
            # 添加标签
            label = f"Face ({features['eye_count']} eyes)"
            if features["is_smiling"]:
                label += " :)"
            
            cv2.putText(result_image, label, (x, y - 10), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
        
        return result_image
    
    def track_face(self, image: np.ndarray, prev_position: Optional[Tuple[int, int]] = None) -> Optional[Dict]:
        """跟踪人脸位置
        
        Args:
            image (numpy.ndarray): 输入图像
            prev_position (tuple, optional): 上一帧的人脸位置
            
        Returns:
            dict: 跟踪结果，包含位置和移动信息，如果未检测到人脸返回None
        """
        # 检测人脸
        faces = self.detect_faces(image)
        
        if not faces:
            return None
        
        # 如果有上一帧位置，选择最接近的人脸
        if prev_position:
            min_distance = float('inf')
            best_face = None
            
            for face in faces:
                x, y, w, h = face
                center = (x + w // 2, y + h // 2)
                distance = np.sqrt((center[0] - prev_position[0])**2 + 
                                  (center[1] - prev_position[1])**2)
                
                if distance < min_distance:
                    min_distance = distance
                    best_face = face
            
            if best_face is None:
                return None
                
            face_rect = best_face
        else:
            # 如果没有上一帧位置，选择最大的人脸
            face_rect = max(faces, key=lambda f: f[2] * f[3])
        
        # 获取人脸特征
        features = self.get_face_features(image, face_rect)
        
        # 计算移动信息
        if prev_position:
            dx = features["position"][0] - prev_position[0]
            dy = features["position"][1] - prev_position[1]
            distance = np.sqrt(dx**2 + dy**2)
            
            features["movement"] = {
                "dx": dx,
                "dy": dy,
                "distance": distance
            }
        else:
            features["movement"] = {
                "dx": 0,
                "dy": 0,
                "distance": 0
            }
        
        return features
    
    def estimate_face_distance(self, face_rect: Tuple[int, int, int, int]) -> float:
        """估算人脸距离
        
        Args:
            face_rect (tuple): 人脸区域矩形 (x, y, w, h)
            
        Returns:
            float: 估算的距离（相对值）
        """
        _, _, w, h = face_rect
        face_size = w * h
        
        # 使用简单的比例关系估算距离
        # 假设人脸大小与距离成反比
        reference_size = 10000  # 参考人脸大小
        estimated_distance = reference_size / face_size
        
        return estimated_distance
    
    def get_face_direction(self, face_rect: Tuple[int, int, int, int], image_size: Tuple[int, int]) -> str:
        """获取人脸相对于图像中心的方向
        
        Args:
            face_rect (tuple): 人脸区域矩形 (x, y, w, h)
            image_size (tuple): 图像大小 (width, height)
            
        Returns:
            str: 方向描述 ("left", "right", "center", "up", "down", "up_left", "up_right", "down_left", "down_right")
        """
        x, y, w, h = face_rect
        face_center_x = x + w // 2
        face_center_y = y + h // 2
        
        image_center_x = image_size[0] // 2
        image_center_y = image_size[1] // 2
        
        # 计算偏移量
        offset_x = face_center_x - image_center_x
        offset_y = face_center_y - image_center_y
        
        # 计算偏移比例
        offset_ratio_x = offset_x / (image_size[0] / 2)
        offset_ratio_y = offset_y / (image_size[1] / 2)
        
        # 确定方向
        threshold = 0.2  # 偏移阈值
        
        if abs(offset_ratio_x) < threshold and abs(offset_ratio_y) < threshold:
            return "center"
        
        direction = ""
        
        if offset_ratio_y < -threshold:
            direction += "up"
        elif offset_ratio_y > threshold:
            direction += "down"
            
        if offset_ratio_x < -threshold:
            direction += "_left" if direction else "left"
        elif offset_ratio_x > threshold:
            direction += "_right" if direction else "right"
        
        return direction